﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using ImageCropper.Converters;
using ImageCropper.Lib;
using System.Windows.Media.Imaging;
using System.IO;

namespace ImageCropper
{
    public partial class CroppingSurface : UserControl
    {
        public static DependencyProperty ZoomProperty = DependencyProperty.Register("Zoom", typeof(double), typeof(CroppingSurface), new PropertyMetadata(1.0, ZoomPropertyChangedCallback));

        private static void ZoomPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            CroppingSurface cs = d as CroppingSurface;
            cs._scaleTransform.ScaleX = (double)e.NewValue;
            cs._scaleTransform.ScaleY = (double)e.NewValue;
        }

        private const int FOCUSED_CROP_ZINDEX = 100;
        private const int UNFOCUSED_CROP_ZINDEX = 0;
        private const int FOCUSED_IMAGE_ZINDEX = -1;
        private const int UNFOCUSED_IMAGE_ZINDEX = -100;

        private bool _isLeftButtonDown = false;
        private Point? _mouseDownPos = null;
        private Point? _rightMouseDownPos = null;
        private Point? _thisCenter = null;

        private ContextMenu _contextMenu;
        private Dictionary<Edge, IGuideline> _guidlines;

        public CroppingSurface()
        {
            InitializeComponent();

            this.Rectangles = new ObservableCollection<ResizableRectangle>();
            this.Rectangles.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Rectangles_CollectionChanged);            
            
            _guidlines = new Dictionary<Edge, IGuideline>();

            _contextMenu = new ContextMenu();

            MenuItem mi;

            mi = new MenuItem() { Header = "Add Crop" };
            mi.Click += new RoutedEventHandler(AddCrop_Click);
            _contextMenu.Items.Add(mi);

            mi = new MenuItem() { Header = "Delete Crop" };
            mi.Click += new RoutedEventHandler(DeleteCrop_Click);
            mi.SetBinding(MenuItem.VisibilityProperty, new System.Windows.Data.Binding("") { Converter = new IsNotNullVisibilityConverter(),  TargetNullValue = "it won't work unless this is set..." });
            _contextMenu.Items.Add(mi);

            _contextMenu.Items. Add(new Separator());

            mi = new MenuItem() { Header = "Add Image" };
            mi.Click += new RoutedEventHandler(AddImage_Click);
            _contextMenu.Items.Add(mi);
        }        
        
        public ObservableCollection<ResizableRectangle> Rectangles
        {
            get;
            private set;
        }

        public double Zoom
        {
            get { return (double)this.GetValue(ZoomProperty); }
            set { this.SetValue(ZoomProperty, value); }
        }

        public void AddCrop(double width, double height)
        {
            AddCrop(20, 20, width, height);
        }
        public void AddCrop(double left, double top, double width, double height)
        {
            CropRectangle crop = new CropRectangle();
            crop.Width = width;
            crop.Height = height;
            crop.Top = top;
            crop.Left = left;

            this.Rectangles.Add(crop);
        }
        public void AddImage(BitmapSource imageSource)
        {
            AddImage(20, 20, imageSource);
        }
        public void AddImage(double left, double top, BitmapSource imageSource)
        {
            ImageRectangle image = new ImageRectangle();
            image.Left = left;
            image.Top = top;
            image.ImageSource = imageSource;

            this.Rectangles.Add(image);
        }        

        private CropRectangle _FindCropParent(object o)
        {
            if (o is CropRectangle)
                return o as CropRectangle;
            if (o is FrameworkElement)
                return _FindCropParent((o as FrameworkElement).Parent);
            else
                return null;
        }
        
        private void AddCrop_Click(object sender, RoutedEventArgs e)
        {            
            if (_rightMouseDownPos.HasValue)
                AddCrop(_rightMouseDownPos.Value.X, _rightMouseDownPos.Value.Y, 400, 300);
            else
                AddCrop(400, 300);
        }
        private void AddImage_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog dialog = new OpenFileDialog();
            dialog.Filter = "image files (*.jpg,*.png)|*.jpg;*.png|All files (*.*)|*.*";
            dialog.Multiselect = false;
            if (dialog.ShowDialog() ?? false)
            {
                BitmapImage bi = new BitmapImage();
                using (BinaryReader sr = new BinaryReader(dialog.File.OpenRead()))
                {
                    bi.SetSource(new MemoryStream(sr.ReadBytes((int)sr.BaseStream.Length)));
                }

                if (_rightMouseDownPos.HasValue)
                    AddImage(_rightMouseDownPos.Value.X, _rightMouseDownPos.Value.Y, bi);
                else
                    AddImage(bi);
            }
        }
        private void DeleteCrop_Click(object sender, RoutedEventArgs e)
        {
            if (_contextMenu.DataContext is CropRectangle)
                this.Rectangles.Remove(_contextMenu.DataContext as CropRectangle);
        }
        private void gl_Hidden(object sender, EventArgs e)
        {
            IGuideline gl = sender as IGuideline;
            gl.Hidden -= new EventHandler(gl_Hidden);
            this._canvas.Children.Remove(gl as UIElement);
        }
        private void rr_GotFocus(object sender, RoutedEventArgs e)
        {
            if (sender is CropRectangle)
                Canvas.SetZIndex(sender as UIElement, FOCUSED_CROP_ZINDEX);
            else if (sender is ImageRectangle)
                Canvas.SetZIndex(sender as UIElement, FOCUSED_IMAGE_ZINDEX);

            foreach (UIElement uie in this._canvas.Children.Where(r => !object.ReferenceEquals(r, sender) && r is CropRectangle))
                Canvas.SetZIndex(uie, UNFOCUSED_CROP_ZINDEX);

            foreach (UIElement uie in this._canvas.Children.Where(r => !object.ReferenceEquals(r, sender) && r is ImageRectangle))
                Canvas.SetZIndex(uie, UNFOCUSED_IMAGE_ZINDEX);
        }
        private void rr_SnappedToEdge(object sender, SnappedToEdgeEventArgs e)
        {
            foreach(Edge edge in this._guidlines.Keys.ToList())
                if (!e.Edges.Contains(edge))
                {
                    IGuideline gl = this._guidlines[edge];
                    this._guidlines.Remove(edge);
                    gl.Hidden += new EventHandler(gl_Hidden);
                    gl.HideLine();
                }

            foreach (Edge edge in e.Edges)
                if (!this._guidlines.ContainsKey(edge))
                {
                    switch (edge.Axis)
                    {
                        case Axis.EastWest:
                            HorizantalGuideline hg = new HorizantalGuideline();
                            hg.Width = Math.Abs(edge.Start.X - edge.End.X);
                            Canvas.SetTop(hg, Math.Min(edge.Start.Y, edge.End.Y));
                            Canvas.SetLeft(hg, Math.Min(edge.Start.X, edge.End.X));
                            this._guidlines.Add(edge, hg);
                            this._canvas.Children.Add(hg);
                            hg.ShowLine();
                            break;

                        case Axis.NorthSouth:
                            VerticalGuideline vg = new VerticalGuideline();
                            vg.Height = Math.Abs(edge.Start.Y - edge.End.Y);
                            Canvas.SetTop(vg, Math.Min(edge.Start.Y, edge.End.Y));
                            Canvas.SetLeft(vg, Math.Min(edge.Start.X, edge.End.X));
                            this._guidlines.Add(edge, vg);
                            this._canvas.Children.Add(vg);
                            vg.ShowLine();
                            break;
                    }
                }
        }
        private void Rectangles_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
                foreach (ResizableRectangle rr in e.NewItems)
                {
                    rr.GotFocus += new RoutedEventHandler(rr_GotFocus);
                    rr.SnappedToEdge += new EventHandler<SnappedToEdgeEventArgs>(rr_SnappedToEdge);
                    rr.Surface = this;
                    this._canvas.Children.Add(rr);
                }

            if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
                foreach (ResizableRectangle rr in e.OldItems)
                {
                    rr.GotFocus -= new RoutedEventHandler(rr_GotFocus);
                    rr.SnappedToEdge -= new EventHandler<SnappedToEdgeEventArgs>(rr_SnappedToEdge);
                    rr.Surface = null;
                    this._canvas.Children.Remove(rr);
                }
        }
        private void UserControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            e.Handled = true;
            this.Focus();

            _isLeftButtonDown = true;
            _mouseDownPos = e.GetPosition(this);
            _thisCenter = new Point(_translateTransform.X, _translateTransform.Y);
            this.Cursor = Cursors.Hand;
        }
        private void UserControl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            _isLeftButtonDown = false;
            _mouseDownPos = null;
            _thisCenter = null;
            this.Cursor = Cursors.Arrow;
        }
        private void UserControl_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            _rightMouseDownPos = e.GetPosition(this._canvas);
            CropRectangle cr = _FindCropParent(e.OriginalSource);
            if (cr != null)
                _contextMenu.DataContext = cr;
            else
                _contextMenu.DataContext = null;

            e.Handled = true;
            _contextMenu.IsOpen = true;
        }        

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if (this.Zoom == 1.0)
                this.Zoom = .5;
            else
                this.Zoom = 1.0;

        }

        private void UserControl_MouseMove(object sender, MouseEventArgs e)
        {
            if (_isLeftButtonDown && _mouseDownPos.HasValue)
            {
                Point mousePos = e.GetPosition(this.Parent as Canvas);
                this._translateTransform.X = _thisCenter.Value.X + mousePos.X - _mouseDownPos.Value.X;
                this._translateTransform.Y = _thisCenter.Value.Y + mousePos.Y - _mouseDownPos.Value.Y;
                this._scaleTransform.CenterX = this._translateTransform.X * -1 * this._scaleTransform.ScaleX;
                this._scaleTransform.CenterY = this._translateTransform.Y * -1 * this._scaleTransform.ScaleY;
            }
        }        
    }
}
